<template>
  <div
    class="video-container"
    @touchstart="onSwipeStart"
    @touchmove="onUpdateTime"
    v-on="{ touchend: [onSwipeEnd, onUpdateTimeEnd] }"
    @mousemove="onUpdateTime"
    @mouseup="onUpdateTimeEnd"
    @click="toggleControl"
  >
    <div class="video-wrap" ref="videoWrap"></div>
    <LoadingCube v-if="isWaiting && isVideoPlay" class="loading-cube"></LoadingCube>
    <Loading v-if="isLoading && !inCard" class="loading-mask bg-background"></Loading>
    <div v-if="isLoading && inCard" class="in-card-loading-mask"></div>
    <ErrorUnknown v-if="errMsg" :ext-message="errMsg"></ErrorUnknown>
    <WaterMark v-if="watermark"></WaterMark>
    <div
      v-if="controlMount"
      class="control"
      ref="control"
      v-show="controlOpen"
      :style="{ transform: controlTransform }"
    >
      <div class="lt pointer" @click.stop="toggleVideoFullscreen(false)">
        <Icon name="back" size="28" shadow></Icon>
      </div>
      <div v-if="!isDesktop" class="rt pointer" @click.stop="toggleVideoMuted">
        <Icon v-if="videoMuted" name="mute" size="28" shadow></Icon>
        <Icon v-else name="unmute" size="28" shadow></Icon>
      </div>
      <div v-if="!isDesktop" class="center gap-row-74">
        <Icon name="back10s" size="30" shadow @click.stop="updateCurrentTime(-10)"></Icon>
        <Icon v-if="!isVideoPlay" name="play" size="50" shadow @click.stop="toggleVideoPlay"></Icon>
        <Icon v-else name="pause" size="50" shadow @click.stop="toggleVideoPlay"></Icon>
        <Icon name="forward10s" size="30" shadow @click.stop="updateCurrentTime(10)"></Icon>
      </div>
      <div class="bc" :class="{ 'mb-8': !isDesktop }" @click.stop>
        <div
          class="time font-size-10 font-weight-500 line-height-120"
          :class="{ 'font-size-14': isDesktop, 'line-height-140': isDesktop }"
        >
          {{ `${formatTime(videoDuration * currRate)} / ${formatTime(videoDuration)}` }}
        </div>
        <div
          class="time-bar pointer"
          ref="timeBar"
          @touchstart.stop="onUpdateTimeStart"
          @mousedown.stop="onUpdateTimeStart"
        >
          <div class="total"></div>
          <div class="curr" :style="{ transform: `scaleX(${currRate})` }">
            <div class="end-point" :style="{ transform: `scaleX(${1 / currRate})` }"></div>
          </div>
        </div>
        <div v-if="isDesktop" class="feature gap-row-16">
          <Icon v-if="!isVideoPlay" class="pointer" name="play" size="24" shadow @click.stop="toggleVideoPlay"></Icon>
          <Icon v-else class="pointer" name="pause" size="24" shadow @click.stop="toggleVideoPlay"></Icon>
          <Icon class="pointer" name="back10s" size="24" shadow @click.stop="updateCurrentTime(-10)"></Icon>
          <Icon class="pointer" name="forward10s" size="24" shadow @click.stop="updateCurrentTime(10)"></Icon>
          <Icon v-if="videoMuted" class="pointer" name="mute" size="24" shadow @click.stop="toggleVideoMuted"></Icon>
          <Icon v-else class="pointer" name="unmute" size="24" shadow @click.stop="toggleVideoMuted"></Icon>
        </div>
      </div>
    </div>
    <div
      v-else
      class="play-pause-control"
      :class="{ play: isVideoPlay, pause: !isVideoPlay, close: playPauseClose }"
      @click="$toggleVideoPlay"
    ></div>
    <div v-if="!controlMount && !inCard" class="no-fullscreen-time-bar">
      <div class="time-bar" :class="{ small: !timeUpdating }">
        <div class="total"></div>
        <div class="curr" :style="{ transform: `scaleX(${currRate})` }">
          <div class="end-point" :style="{ transform: `scaleX(${1 / currRate})` }"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, watch } from 'vue'
import { useElementBounding, useTimeoutFn, useWindowFocus } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { useAppStore } from '@/store/app'
import { useVideoTimeBarStore } from '@/store/video-time-bar'
import { $t } from '@/i18n'
import { get, release } from '@/utils/video-store'
import ErrorUnknown from '@/components/common/ErrorUnknown.vue'
import Loading from '@/components/common/Loading.vue'
import LoadingCube from '@/components/common/LoadingCube.vue'
import WaterMark from '@/components/common/WaterMark.vue'
import { usePreventTouchmove } from '@/compositions/mobile/preventTouchmove'
import { useStat } from '@/compositions/viewer/stat'
import { MEDIA_TYPE } from '@/constant'
import VideoStoreEmptyError from '@/errors/VideoStoreEmptyError'
import { trackEvent } from '@/gtm'
import { debounce } from 'lodash-es'

const videoWrap = ref(null)
const videoElement = ref(null)
const videoCurrentTime = ref(0)
const videoDuration = ref(0)
const isLoading = ref(true)
const isWaiting = ref(false)
const errMsg = ref('')
const { start: toWaiting, stop: cancelToWaiting } = useTimeoutFn(() => (isWaiting.value = true), 500, {
  immediate: false,
})

const progressTracked = {
  0.25: false,
  0.5: false,
  0.75: false,
  1.0: false,
}

const props = defineProps({
  url: { type: String },
  active: { type: Boolean, default: true },
  ready: { type: Boolean, default: true },
  statInfo: { type: Object, required: false },
  preview: { type: Boolean, default: false },
  inCard: { type: Boolean, default: false },
  watermark: { type: Boolean, default: false },
})
// 元件外操作更新時間軸
const videoTimeBarStore = useVideoTimeBarStore()
const { rate, seeking, currentPlayRate, currentPlayTime } = storeToRefs(videoTimeBarStore) 
const emits = defineEmits(['play', 'ended', 'seek', 'pause', 'update'])

// 播放/暫停 全域共用變數
const appStore = useAppStore()
const { isDesktop, videoPlay, videoFullscreen, videoMuted, isMobile } = storeToRefs(appStore)
const { toggleVideoPlay: $toggleVideoPlay, toggleVideoFullscreen, toggleVideoMuted: $toggleVideoMuted } = appStore

const isVideoPlay = computed(() => videoPlay.value || props.inCard)

function updateCurrentTime(second) {
  const v = Math.min(videoDuration.value, Math.max(0, videoElement.value.currentTime + second))
  videoElement.value.currentTime = v
  setupControlOpen()
}
function formatTime(time) {
  return `${String(Math.floor(time / 60)).padStart(2, '0')}:${String(Math.floor(time % 60)).padStart(2, '0')}`
}
function toggleVideoPlay() {
  $toggleVideoPlay()
  setupControlOpen()
}
function toggleVideoMuted() {
  $toggleVideoMuted()
  setupControlOpen()
}

function setupVideo() {
  if (!props.ready) return

  try {
    trySetupVideo()
  } catch (e) {
    if (e instanceof VideoStoreEmptyError) {
      console.log('不夠用，下一幀再嘗試')
      requestAnimationFrame(() => {
        try {
          trySetupVideo()
        } catch (e) {
          setupVideoError(e.message)
        }
      })
    } else {
      setupVideoError(e.message)
    }
  }
}

// 調用此函式的地方必須要 try catch
// get 的時候有可能會因為 vue 先建立新的元件之後才把沒用到的元件卸載
// 此時可能會暫時發生 video 元件不夠用的情況會拋出 Error
// 但合理情況內應該是等到下一幀再嘗試 get 就不會有問題了
function trySetupVideo() {
  if (!props.url) return

  videoElement.value = get(props.url, {
    currentTime: videoCurrentTime.value,
    onLoaded: () => (isLoading.value = false),
    onWaiting: toWaiting,
    onPlaying: () => {
      cancelToWaiting()
      isWaiting.value = false
      if (!videoFullscreen.value) {
        requestAnimationFrame(playOrPauseVideo) // 沒有等下一幀會無法真的觸發play/pause，可能跟video裡的狀態有關
      }
    },
    onPlay: () => {
      emits('play', { currentPlayTime: videoElement.value?.currentTime || 0 })
    },
    onEnded: () => {
      
      if (videoDuration.value){
        emits('ended', { currentPlayTime: videoDuration.value * 1000 })
      }
      videoTimeBarStore.reset()
    },
    onTimeupdate: (e) => {
      if (timeUpdating.value) return
      // 響應視頻時間更新
      videoCurrentTime.value = videoElement.value?.currentTime || 0
      currRate.value = videoDuration.value === 0 ? 0 : videoCurrentTime.value / videoDuration.value
      videoTimeBarStore.updateRate(currRate.value)
      videoTimeBarStore.updateTime(videoCurrentTime.value * 1000)
      emits('update', { currentPlayTime: videoElement.value?.currentTime || 0 })

      // gtm 判別發送
      if (!props.statInfo) {
        return
      }
      const progress = e.target.currentTime / e.target.duration
      for (const [percentage, tracked] of Object.entries(progressTracked)) {
        if (!tracked) {
          const p = parseFloat(percentage)
          if (p !== 1.0) {
            if (progress >= p) {
              trackEvent({ key: 9, value: p, meta: { id: props.statInfo.id } })
              progressTracked[p] = true
            }
          } else {
            if (progressTracked[0.25] && progressTracked[0.5] && progressTracked[0.75] && progress < 0.75) {
              trackEvent({ key: 9, value: 1.0, meta: { id: props.statInfo.id } })
              progressTracked[1.0] = true 
              // emits('ended', { currentPlayTime: (videoElement.value?.currentTime || 0) * 1000 }) 此处触发没有意义 时间已重置为 0
            }
          }
        }
      }
    },
    onError: (e) => {
      setupVideoError($t('common.loadSourceError'))
    },
    isPreview: props.preview,
    isInCard: props.inCard,
  })
  videoWrap.value.prepend(videoElement.value)

  // 視頻時長更新回呼
  videoElement.value.ondurationchange = () => (videoDuration.value = videoElement.value?.duration || 0)

  playOrPauseVideo()
}
function setupVideoError(msg) {
  isLoading.value = false
  errMsg.value = msg
}

function releaseVideo() {
  if (!videoElement.value) return

  // 清掉客製掛上去的視頻時長更新回呼
  videoElement.value.ondurationchange = undefined

  // 紀錄當前播放到的時間，下次回來直接從此處開始播
  videoCurrentTime.value = videoElement.value.currentTime

  release(videoElement.value)
  videoElement.value = null
  isLoading.value = true
  errMsg.value = ''
}

// 適當的時機安插/釋放視頻
onMounted(setupVideo)
onUnmounted(releaseVideo)
onActivated(setupVideo)
onDeactivated(releaseVideo)

// 視頻地址改變時先釋放再重新安插視頻
watch(
  () => props.url,
  () => {
    releaseVideo()
    setupVideo()
  },
)

watch(
  () => props.ready,
  (ready) => (ready ? setupVideo() : releaseVideo()),
)
const focused = useWindowFocus()
watch(focused, (focus) => {
  if (isMobile.value) {
    if (focus) {
      setupVideo()
    } else {
      releaseVideo()
    }
  }
})

// 適當的時機播放/暫停視頻
watch(isVideoPlay, () => {
  playOrPauseVideo()
  openPlayPause()
})

let timeoutId
watch(
  () => props.active,
  () => {
    // 等轉場效果結束再開始播放，不然有些手機可能會頓一下
    clearTimeout(timeoutId)
    timeoutId = setTimeout(playOrPauseVideo, 350)
  },
)
let playPromise
function playOrPauseVideo() {
  if (!videoElement.value) return

  const isPlay = isVideoPlay.value && props.active

  if (playPromise) {
    if (!isPlay) {
      playPromise.finally(videoElement.value.pause)
    }
    return
  }

  playPromise = isPlay ? videoElement.value.play() : videoElement.value.pause()
  if (!isPlay && videoElement.value?.currentTime > 0) {
    emits('pause', { currentPlayTime: videoElement.value.currentTime })
  }

  if (playPromise) {
    playPromise
      .catch(() => {
        if (isPlay && videoElement.value) {
          videoElement.value.play()
        }
      })
      .finally(() => (playPromise = undefined))
  }
}

// 非全屏時視頻播放/暫停顯示後離場
const PLAY_PAUSE_CLOSE_TIMEOUT = 500
const playPauseClose = ref(isVideoPlay.value)
function openPlayPause() {
  clearTimeout(openPlayPause.timeoutId)
  playPauseClose.value = false
  if (isVideoPlay.value) {
    openPlayPause.timeoutId = setTimeout(() => (playPauseClose.value = true), PLAY_PAUSE_CLOSE_TIMEOUT)
  }
}

// 阻止全屏時手機上下滑的時候會飄動
const control = ref(null)
usePreventTouchmove(control)

// 全屏時間軸拖拉
const currRate = ref(0)
const timeBar = ref(null)
const {
  left: timeBarLeft,
  right: timeBarRight,
  width: timeBarWidth,
  update: updateTimeBarBounding,
} = useElementBounding(timeBar)
const timeUpdating = ref(false)
function onUpdateTimeStart(evt) {
  evt.stopPropagation()
  if (timeUpdating.value) {
    return
  }
  if (timeBarWidth.value === 0) {
    return
  }

  timeUpdating.value = true
  videoElement.value?.pause()
  updateCurrRate(evt.pageX || evt.touches[0].pageX)
  setupControlOpen()
}

const updateCurrRateDebounced = debounce((pageX) => {
  updateCurrRate(pageX)
}, 200)

function onUpdateTime(evt) {
  evt.stopPropagation()
  if (!timeUpdating.value) {
    return
  }
  updateCurrRateDebounced(evt.pageX || evt.touches[0].pageX)
  setupControlOpen()
}
function onUpdateTimeEnd(evt) {
  evt.stopPropagation()
  if (!timeUpdating.value) {
    return
  }
  timeUpdating.value = false
  videoElement.value.currentTime = videoDuration.value * currRate.value
  playOrPauseVideo()
  setupControlOpen()
}
function updateCurrRate(pageX) {
  const x = Math.min(timeBarRight.value, Math.max(0, pageX - timeBarLeft.value))
  currRate.value = x / timeBarWidth.value
}


watch(seeking,(newSeeking) => {
  if (currRate.value && newSeeking === false){
    console.log('from',videoDuration.value * currentPlayRate.value * 1000,'to', videoDuration.value * currRate.value * 1000)
    if (currRate.value < 1){
      emits('seek', {
        start_time:parseInt(videoDuration.value * currentPlayRate.value * 1000),
        target_time: parseInt(videoDuration.value * currRate.value * 1000), // 拖曳的目标时间点（毫秒）
        timestamp: Date.now(), // Unix 毫秒
      })
      
      
    }
    else{
      videoElement.value.currentTime = videoDuration.value * currRate.value
    }
    videoTimeBarStore.clear()
    playOrPauseVideo()
    setupControlOpen()
    timeUpdating.value = false
  }
})

watch(rate, (newRate) => {

  if (!props.active) return
  if ((+currRate.value).toFixed(8) === (+newRate).toFixed(8)) return
  
  if (newRate !== null){
    timeUpdating.value = true
    videoElement.value?.pause()
    currRate.value = newRate
    videoElement.value.currentTime = videoDuration.value * currRate.value
    setupControlOpen()
  }

  
})

// 全屏控制器進場離場效果
const controlMount = ref(false)
const controlTransform = ref('')
function controlTransition(enter) {
  controlTransform.value = `scale(${0})`
  if (enter) {
    requestAnimationFrame(() => (controlTransform.value = ''))
  }
}
watch(videoFullscreen, (v) => {
  if (v) {
    controlTransition(true)
    controlMount.value = true
    setTimeout(updateTimeBarBounding, 320)
  } else {
    controlTransition()
    setTimeout(() => (controlMount.value = false), 300)
  }
})

// 全屏操作離場
const CONTROL_CLOSE_TIMEOUT = 3000
const controlOpen = ref(false)
function toggleControl(open) {
  if (typeof open === 'boolean') {
    controlOpen.value = open
  } else {
    controlOpen.value = !controlOpen.value
  }
  if (controlOpen.value) {
    setupControlOpen()
  }
}
function setupControlOpen() {
  clearTimeout(setupControlOpen.timeoutId)
  if (isVideoPlay.value && !timeUpdating.value) {
    setupControlOpen.timeoutId = setTimeout(() => (controlOpen.value = false), CONTROL_CLOSE_TIMEOUT)
  }
}
watch(videoFullscreen, (v) => {
  if (v) {
    toggleControl(true)
  }
})

// 上下滑動關閉全屏
let touchStartY
let touchEndY
function onSwipeStart(evt) {
  evt.stopPropagation()
  if (!touchStartY) {
    touchStartY = evt.changedTouches[0].screenY
    clearTimeout(setupControlOpen.timeoutId)
  }
}
function onSwipeEnd(evt) {
  evt.stopPropagation()
  if (touchStartY && !touchEndY) {
    touchEndY = evt.changedTouches[0].screenY
    if (Math.abs(touchStartY - touchEndY) > 50) {
      toggleVideoFullscreen(false)
    } else {
      setupControlOpen()
    }
    touchStartY = undefined
    touchEndY = undefined
  }
}

// 統計觀看數據
if (props.statInfo) {
  useStat(
    MEDIA_TYPE.VIDEO,
    () => props.active,
    () => props.statInfo,
    {
      activeMs: () => Math.floor((videoElement.value ? videoElement.value.currentTime : videoCurrentTime.value) * 1000),
    },
  )
}
</script>

<style lang="scss" scoped>
@import '@/assets/styles/loading-skeleton.scss';
@import './video.scss';
</style>
